package com.kalvan.log.logback.web;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.selector.ContextSelector;
import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.classic.util.ContextSelectorStaticBinder;
import ch.qos.logback.core.joran.spi.JoranException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.impl.StaticLoggerBinder;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ResourceBundle;

/**
 * @author chenliang
 * @see LogbackConfigListener
 */
public class LogbackConfigListener implements ServletContextListener {
    public static final String LOG_PATH = "logback.logPath";
    public static final String ROOT_LOGGER_LEVEL = "logback.rootLoggerLevel";
    public static final String MAX_FILE_SIZE = "logback.maxFileSize";
    public static final String LOGBACK_PROPERTIES = "logbackProperties";
    public static final String CONFIG_LOCATION_PARAM = "logbackConfigLocation";

    /**
     * Pseudo URL prefix for loading from the class path: "classpath:"
     */
    public static final String CLASSPATH_URL_PREFIX = "classpath:";
    public static final String PROPERTIES = ".properties";

    /**
     * 注入动态配置属性<br>
     * 编码默认是utf-8 <br>
     * 输出格式统一%date{yyyy-MM-dd HH:mm:ss.SSS}|%-5level
     * |%t:%threadid|%logTraceId|%class.%method:%line|%sensitiveMsg%n
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        ServletContext context = event.getServletContext();
        // 查找logback属性配置文件
        if (StringUtils.isNotBlank(context.getInitParameter(LOGBACK_PROPERTIES))) {
            String path = context.getInitParameter(LOGBACK_PROPERTIES);
            if (path.endsWith(PROPERTIES)) {
                path = path.replace(PROPERTIES, "");
            }
            ResourceBundle resource = ResourceBundle.getBundle(path);
            System.setProperty(LOG_PATH, resource.getString(LOG_PATH).trim());
            System.setProperty(ROOT_LOGGER_LEVEL,
                    resource.getString(ROOT_LOGGER_LEVEL).trim());
            System.setProperty(MAX_FILE_SIZE, resource.getString(MAX_FILE_SIZE)
                    .trim());
        } else {
            // 注入动态配置属性-日志路径
            if (StringUtils.isNotBlank(context.getInitParameter(LOG_PATH))) {
                System.setProperty(LOG_PATH, context.getInitParameter(LOG_PATH)
                        .trim());
            }
            // 注入动态配置属性-root日志级别
            if (StringUtils.isNotBlank(context
                    .getInitParameter(ROOT_LOGGER_LEVEL))) {
                System.setProperty(ROOT_LOGGER_LEVEL,
                        context.getInitParameter(ROOT_LOGGER_LEVEL).trim());
            }
            // 注入动态配置属性-文件分隔大小
            if (StringUtils.isNotBlank(context.getInitParameter(MAX_FILE_SIZE))) {
                System.setProperty(MAX_FILE_SIZE,
                        context.getInitParameter(MAX_FILE_SIZE).trim());
            }
        }
        String location = context.getInitParameter(CONFIG_LOCATION_PARAM);
        /***** 方法一加载，使用原生方式 *******/
        try {
            URL url = getUrl(location);
            LoggerContext loggerContext = (LoggerContext) StaticLoggerBinder
                    .getSingleton().getLoggerFactory();
            // in the current version logback automatically configures at
            // startup
            // the context, so we have to reset it
            loggerContext.reset();
            // reinitialize the logger context. calling this method allows
            // configuration through groovy or xml
            new ContextInitializer(loggerContext).configureByResource(url);
        } catch (FileNotFoundException ex) {
            context.log("No logback configuration file found at [" + location
                    + "]");
        } catch (JoranException e) {
            throw new RuntimeException(
                    "Unexpected error while configuring logback", e);
        }

        /**
         * 方法二加载，使用spring方式
         * sring 集成 时使用 <dependency> <groupId>org.logback-extensions</groupId>
         * <artifactId>logback-ext-spring</artifactId> </dependency>
         */
        // WebLogbackConfigurer.initLogging(event.getServletContext());
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        ContextSelector selector = ContextSelectorStaticBinder.getSingleton()
                .getContextSelector();
        LoggerContext loggerContext = selector.getLoggerContext();
        String loggerContextName = loggerContext.getName();
        LoggerContext context = selector.detachLoggerContext(loggerContextName);
        context.reset();
    }

    /**
     * Resolve the given resource location to a {@code java.net.URL}.
     * <p>
     * Does not check whether the URL actually exists; simply returns the URL
     * that the given location would correspond to.
     *
     * @param resourceLocation the resource location to resolve: either a "classpath:" pseudo
     *                         URL, a "file:" URL, or a plain file path
     * @return a corresponding URL object
     * @throws FileNotFoundException if the resource cannot be resolved to a URL
     */
    private static URL getUrl(String resourceLocation)
            throws FileNotFoundException {
        if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
            String path = resourceLocation.substring(CLASSPATH_URL_PREFIX
                    .length());
            URL url = Thread.currentThread().getContextClassLoader()
                    .getResource(path);
            if (url == null) {
                String description = "class path resource [" + path + "]";
                throw new FileNotFoundException(
                        description
                                + " cannot be resolved to URL because it does not exist");
            }
            return url;
        }
        try {
            // try URL
            return new URL(resourceLocation);
        } catch (MalformedURLException ex) {
            // no URL -> treat as file path
            try {
                return new File(resourceLocation).toURI().toURL();
            } catch (MalformedURLException ex2) {
                throw new FileNotFoundException("Resource location ["
                        + resourceLocation
                        + "] is neither a URL not a well-formed file path");
            }
        }
    }
}